package com.leen.security;

import com.alibaba.fastjson.JSONObject;
import com.leen.security.domain.AdminLoginUserDetails;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 管理jwt令牌 以及  自定义AdminLoginUser
 *
 * @author admin
 * @date 2021-05-13
 */
@Log4j2
@Component
public class TokenService {
    /**
     * 令牌有效期（默认30分钟）
      */
    private int expireTime=30;

    /**
     * vue每次请求推荐令牌名称  header["X-Token"]="Bearer xxx-xxx-xxx-xxx"
     */
    private final String HEADER_TOKEN="X-Token";

    /**
     * 登录用户 redis key 存储前缀
     */
    public static final String LOGIN_TOKEN_KEY = "adminlogin:tokens:";

    /**
     * 令牌值的前缀
     */
    public static final String TOKEN_PREFIX = "Bearer ";

    /**
     * jwt令牌秘钥(这个不能泄露)
     */
    private static final String secret="89#&457!!@78@@8833&&22_^22!!!";

    @Autowired
    private StringRedisTemplate  stringRedisTemplate;

    /**
     * 把登录用户设定UUID，然后再设定过期时间30分钟，然后 把Token以及AdminLoginUser存储到redis中
     * 最后把uuid使用jwt进行加密，发送给前端
     *
     * @param adminLoginUser 当前用户登录的类
     * @return 令牌
     */
    public String createToken(AdminLoginUserDetails adminLoginUser)
    {
        //唯一的标识 (redis会用到的)
        String token = UUID.randomUUID().toString();
        adminLoginUser.setToken(token);

         refreshToken(adminLoginUser);

         //开始生成jwt
        Map<String, Object> claims = new HashMap<>();
        claims.put(LOGIN_TOKEN_KEY,token);

        //把uuid 数字 变为了  jwt加密的字符
       return createToken(claims);

    }

    /**
     * 从数据声明生成令牌
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }



    /**
     * 令设登录用户过期时候 。并且把它存储到redis中
     *
     * @param adminLoginUser 登录信息
     */
    public void refreshToken(AdminLoginUserDetails adminLoginUser)
    {

        adminLoginUser.setLoginTime(System.currentTimeMillis());

        // 把30分换为 毫秒  =1分钟=60秒 *1000毫秒
        long halfhour_millisecond=expireTime*60L*1000;
        adminLoginUser.setExpireTime(adminLoginUser.getLoginTime() + halfhour_millisecond);

        // 根据uuid换成 redis 中存储键
        String redisKey = getTokenKey(adminLoginUser.getToken());

        //把登录用户存储到redis中去
        String redisValue= JSONObject.toJSONString(adminLoginUser);

        //存储到redis中去
        this.stringRedisTemplate.opsForValue().set(redisKey,redisValue, expireTime, TimeUnit.MINUTES );
    }

    /**
     * 把UUID 转换成 redis中的UUID
     * @param uuid
     * @return
     */
    private String getTokenKey(String uuid)
    {
        return  LOGIN_TOKEN_KEY + uuid;
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return token
     */
    private String getToken(HttpServletRequest request)
    {
        String token = request.getHeader(HEADER_TOKEN);

        if (StringUtils.isNotEmpty(token) && token.startsWith( TOKEN_PREFIX))
        {
            token = token.replace(TOKEN_PREFIX, "");
        }
        return token;
    }

    /**
     * 根据请求头推荐 token令牌，判断用户是否登陆过
     *  Bearer ABVCAEE_222121212111212121212121212121212121  JWT(UUID字符串)
     * @param request
     * @return
     */
    public AdminLoginUserDetails getAdminLoginUser(HttpServletRequest request) {

        AdminLoginUserDetails   adminLoginUser=null;

       //得到JWT(UUID) 密文
        String jwtStr=getToken(request);
        if(StringUtils.isNotBlank(jwtStr)){

            log.debug("令牌:{}",jwtStr);
            //map集合
            Claims  claims=decodeJwtToken(jwtStr);
            /*
            之前这样存在
             Map<String, Object> claims = new HashMap<>();
              claims.put(LOGIN_TOKEN_KEY,token);
             */
            //取得uuid
            String uuid=claims.get(LOGIN_TOKEN_KEY).toString();
            //开始向redis中 查询是否存在
            String redisKey=getTokenKey(uuid);
            //redis中查
            String redisValue=this.stringRedisTemplate.opsForValue().get(redisKey);
            //json串反序列化成 java对象
            if(StringUtils.isNotBlank(redisValue)){
                adminLoginUser= JSONObject.parseObject(redisValue, AdminLoginUserDetails.class);
            }
        }
        return adminLoginUser;

    }

    /**
     * 把jwt密文 解码为明文
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims decodeJwtToken(String token)
    {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 20分钟上
     */
    private static final Long TEN_MILLIS = 10 * 60 * 1000L;

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param adminLoginUser
     * @return 令牌
     */
    public void verifyToken(AdminLoginUserDetails adminLoginUser)
    {
        // 原本的结束时间毫秒
        long expireTime = adminLoginUser.getExpireTime();
        //当前系统时间
        long currentTime = System.currentTimeMillis();
        //相差  <10你分钟
        if (expireTime - currentTime <= TEN_MILLIS)
        {
            //重新续时间半小时
            refreshToken(adminLoginUser);
        }
    }


}
